#!/usr/bin/perl

=head1 NAME

Swignition::DataModel::Model - Container for one or more graphs.

=head1 SYNOPSIS

  use Data::Dumper;
  use Swignition::DataModel;

  my $model  = Swignition::DataModel::Model->new;
  my $graph1 = Swignition::DataModel::Graph->new($model);
  my $graph2 = Swignition::DataModel::Graph->new($model);
  
  $model->combined_graph->add_uri('http://example.com/combined.graph');
  
  my $triple = $graph1->quick_rrr('_:Bob',
                     'http://xmlns.com/foaf/0.1/knows',
		     '_:Dave');
		     
  # The _:Bob here is a different Bob.
  $graph2->quick_rrr('_:Bob',
                     'http://xmlns.com/foaf/0.1/knows',
		     '_:Kevin');
  $graph2->quick_rrl('_:Bob',
		     'http://xmlns.com/foaf/0.1/name',
		     'Robert',
		     undef,
		     'en');

  # But we can add a triple to graph2 which includes the original Bob!
  $graph2->quick_rrr('_:Bob',
                    'http://xmlns.com/foaf/0.1/knows',
		    $triple->subject);
		     
  print Dumper($model->combined_graph->as_json('g')) . "\n";

=head1 DESCRIPTION

The previous Swignition model for data (C<Swignition::RDFModel>) was
capable of storing data which conformed to the RDF model of semantics.
The new C<Swignition::DataModel> is capable of representing data
beyond the standard RDF model. For example, literal subjects are
supported, as are multiple graphs.

The scope for C<Swignition::DataModel> is roughly the same as
Notation 3 L<http://www.w3.org/DesignIssues/Notation3> with the following
differences:

=over

=item * No namespaces

Namespaces may be used by parsers and serialisers, but have no place in
the data model. Full URIs must be used.

=item * Supports reification

The C<Swignition::DataModel::Triple> class is a subclass of
C<Swignition::DataModel::Node>, so a triple may itself be the subject or
object of another triple (though a triple is prevented from acting as the
predicate of another triple). A triple may be given a URI (and if not,
exists as a blank node)

=item * No support for paths

These are just a notational shortcut thing. You can just expand them into
full triples and add them to the model that way.

=item * No support for variables (?x) or quantification (@forAll, @forSome)

C<Swignition::DataModel> is not intended as a reasoning engine: just a
data storage model.

There are no plans to offer support for coping with Notation 3
quantification statements. Support for variables of some kind may
appear in the query interface at a later date, but they are not
currently supported.

=item * Triples may belong to more than one graph.

A triple may belong to more than one graph. This is different to saying
that one graph has a triple with a certain subject, predicate and object
and another graph has a triple with the same subject, predicate and
object. In C<Swignition::DataModel> these may or may not be represented
as the same in-memory triple which belongs to multiple graphs. This
distinction becomes pretty important once reification comes into play.

=item * Blank nodes may belong to more than one graph.

One graph may have a node _:foo, and another graph also have a node _:foo
with those two nodes being totally different resources, and that is fine.
But C<Swignition::DataModel> is also capable of representing the situation
where _:foo in both graphs are the actually same resource.

If this worries you, don't. You actually have to go to quite a lot of
effort to trigger this without meaning to. You should be able to use blank
nodes happily without any dire consequences.

=item * A resource can have multiple URIs.

Think of this as transparent support for owl:sameAs. Indeed, if you add
a triple to a graph which asserts that two resources are owl:sameAs each
other, then the two nodes are merged in-memory (for all triples in all
graphs).

=item * A combined graph is automatically created.

Whenever a triple is added to any graph in the model, it is automatically
added to an uber-graph. If you wish to use this graph for any reasoning,
then it is inadvisable to create any graphs which you know to contain false
statements.

=back

Generally speaking, you don't want to do much with
C<Swignition::DataModel::Model> directly. Create a model, create one or
more graphs in the model and then do what you want with the graphs.

=cut

package Swignition::DataModel::Model;

use Carp;
use URI::URL;

use constant TRUE      =>  1;
use constant FALSE     =>  0;
use strict;

our $VERSION = '0.1';

=head1 CONSTRUCTORS

=over

=item $model = Swignition::DataModel::Model->new

Create a new model. You only want one model.

=cut

sub new
{
	my $class = shift;
	my $model = shift;
	
	my $this = {
		'_' => {
			'usage' => {
				'has_node'     => [],   # list of nodes
				'has_graph'    => [],   # list of graphs
			},
			'fast_find' => {},
			'combined' => 1,
			'named_nodes' => {},
		},
	};
	
	bless $this, $class;
	
	$this->{'_'}->{'combined'} = Swignition::DataModel::Graph->new($this);
	
	# Tell the combined graph that it is the combined graph!
	$this->combined_graph->{'_'}->{'combined'} = TRUE;
	
	return $this;
}

=back

=head1 PUBLIC METHODS

=over

=item $model->combined_graph

Returns the big combined graph.

=cut

sub combined_graph
{
	my $this = shift;
	return $this->{'_'}->{'combined'};
}

=item $model->add_node($node)

Adds a node to the model.

=cut

sub add_node
{
	my $this = shift;
	my $node = shift;
	
	return $this if ($node->{'_'}->{'in_model'} == $this);
	
	# Look for potentials to merge with
	my @mergeWith;
	
	# For each URI the new node has...
	foreach my $nodeuri ($node->uris)
	{
		my $existing = $this->{'_'}->{'fast_find'}->{$nodeuri};
		
		# If there's already a node with that URI, but it's
		# not the same as this one...
		if (defined $existing && !$node->same_as($existing))
		{
			# Check if we already know about it
			my $knowIt = 0;
			foreach my $m (@mergeWith)
				{ $knowIt = 1 if $m->same_as($existing); }
				
			# If we don't then add it to the merge queue.
			push @mergeWith, $existing unless $knowIt;
		}
	}
	
	# Do merges
	foreach my $existing (@mergeWith)
	{
		$node->merge_with($existing);
	}
	
	# Make node available in fast find.
	foreach my $nodeuri ($node->uris)
	{
		$this->{'_'}->{'fast_find'}->{$nodeuri} = $node;
	}
	
	# This will occasionally create duplicates which should be deleted really.
	push @{ $this->{'_'}->{'usage'}->{'has_node'} }, $node;
	
	# Remember that the node is in the model to avoid re-adding it.
	$node->{'_'}->{'in_model'} = $this;
	
	return $this;
}

=item $model->nodes

Get a list of all nodes used in this model.

=cut

sub nodes
{
	my $this = shift;
	return $this->{'_'}->{'usage'}->{'has_node'};
}

=item $model->graphs

Get a list of all graphs used in this model.

=cut

sub graphs
{
	my $this = shift;
	return $this->{'_'}->{'usage'}->{'has_graph'};
}


=item $node = $model->lookup_node($uri, $mint)

Find the node representing $uri. If no such node is found and $mint is true
then will create a node to represent the URI.

=cut

sub lookup_node
{
	my $this = shift;
	my $uri  = shift;
	my $mint = shift || 0;
	
	# Fast find will hopefully find the node pretty quickly.
	if (defined $this->{'_'}->{'fast_find'}->{$uri})
	{
		my $possible = $this->{'_'}->{'fast_find'}->{$uri};
		return $possible if ($possible->answers_to($uri));
	}
	
	# Otherwise check each node.
	foreach my $possible (@{$this->nodes})
	{
		next unless $possible->answers_to($uri);
		$this->{'_'}->{'fast_find'}->{$uri} = $possible;
		return $possible;
	}
	
	# If still not found, we need to mint a new node.
	if ($mint)
	{
		my $rv = Swignition::DataModel::Resource->new($this, $uri);
		$this->{'_'}->{'fast_find'}->{$uri} = $rv;
		return $rv;
	}
	
	return undef;
}

=item $accepted_name = $model->register_node_name($node, $suggested_name)

Create a name for a (usually blank) node which is unique within the model.

=cut

sub register_node_name
{
	my $this = shift;
	my $node = shift;
	my $name = shift || 'BlankNode';
	
	$name =~ s/^_://;
	
	# Never allow a node to register itself with name matching /^n\d*/ because
	# we use that internally to generate unique names for blank nodes.
	$name =~ s/^(n)(\d*)/x\1\2/i;
	
	if (defined $this->{'_'}->{'named_nodes'}->{$name}
	&&  $node->same_as($this->{'_'}->{'named_nodes'}->{$name}))
	{
		return $name;
	}
	
	if (defined $this->{'_'}->{'named_nodes'}->{$name})
	{
		my $format = '%s_%03d';
		my $number = 0;
		my $stump  = $name;
		if ($name =~ /^(.+?)[_-]?(\d+)$/)
		{
			$stump  = $1;
			$number = $2;
		}
		
		while (defined $this->{'_'}->{'named_nodes'}->{$name})
		{
			$number++;
			$name = sprintf($format, $stump, $number);
		}
	}
	
	$this->{'_'}->{'named_nodes'}->{$name} = $node;
	return $name;
}

=back

=head1 SEE ALSO

L<Swignition::DataModel::Graph>, L<Swignition::DataModel::Triple>,
L<Swignition::DataModel::Node>, L<Swignition::DataModel::BlankNode>,
L<Swignition::DataModel::Literal>, L<Swignition::DataModel::XMLLiteral>,
L<Swignition::DataModel::Resource>

L<http://www.w3.org/DesignIssues/Notation3>

L<http://www.w3.org/TR/rdf-concepts/>

L<http://buzzword.org.uk/swignition/>

=cut

1;
